; Demonstrates the interaction of the split screen and
; horizontal pel panning. On a VGA, first pans right in the top
; half while the split screen jerks around, because split screen
; pel panning suppression is disabled, then enables split screen
; pel panning suppression and pans right in the top half while the
; split screen remains stable. On an EGA, the split screen jerks
; around in both cases, because the EGA doesn't support split
; screen pel panning suppression.
;
; The jerking in the split screen occurs because the split screen
; is being pel panned (panned by single pixels--intrabyte panning),
; but is not and cannot be byte panned (panned by single bytes--
; "extrabyte" panning) because the start address of the split screen
; is forever fixed at 0.
;*********************************************************************
IS_VGA	equ	1		;set to 0 to assemble for EGA
;
VGA_SEGMENT	equ	0a000h
LOGICAL_SCREEN_WIDTH equ 1024	;# of pixels across virtual
				; screen that we'll pan across
SCREEN_HEIGHT	equ	350
SPLIT_SCREEN_START equ	200	;start scan line for split screen
SPLIT_SCREEN_HEIGHT equ SCREEN_HEIGHT-SPLIT_SCREEN_START-1
CRTC_INDEX	equ	3d4h	;CRT Controller Index register
AC_INDEX		equ	3c0h	;Attribute Controller Index reg
OVERFLOW		equ	7	;index of Overflow reg in CRTC
MAXIMUM_SCAN_LINE equ	9	;index of Maximum Scan Line register
				; in CRTC
START_ADDRESS_HIGH equ	0ch	;index of Start Address High register
				; in CRTC
START_ADDRESS_LOW equ	0dh	;index of Start Address Low register
				; in CRTC
HOFFSET		equ	13h	;index of Horizontal Offset register
				; in CRTC
LINE_COMPARE	equ	18h	;index of Line Compare reg (bits 7-0
				; of split screen start scan line)
				; in CRTC
AC_MODE_CONTROL equ	10h	;index of Mode Control reg in AC
PEL_PANNING	equ	13h	;index of Pel Panning reg in AC
INPUT_STATUS_0	equ	3dah	;Input Status 0 register
WORD_OUTS_OK	equ	1	;set to 0 to assemble for
				; computers that can't handle
				; word outs to indexed VGA registers
;*********************************************************************
; Macro to output a word value to a port.
;
OUT_WORD	macro
if WORD_OUTS_OK
	out	dx,ax
else
	out	dx,al
	inc	dx
	xchg	ah,al
	out	dx,al
	dec	dx
	xchg	ah,al
endif
	endm
;*********************************************************************
MyStack segment para stack 'STACK'
	db	512 dup (0)
MyStack ends
;*********************************************************************
Data	segment
SplitScreenLine dw	?	;line the split screen currently
				; starts after
StartAddress	dw	?	;display memory offset at which
				; scanning for video data starts
PelPan		db	?	;current intrabyte horizontal pel
				; panning setting
Data	ends
;*********************************************************************
Code	segment
	assume	cs:Code, ds:Data
;*********************************************************************
Start	proc	near
	mov	ax,Data
	mov	ds,ax
;
; Select mode 10h, 640x350 16-color graphics mode.
;
	mov	ax,0010h	;AH=0 is select mode function
				;AL=10h is mode to select,
				; 640x350 16-color graphics mode
	int	10h
;
; Set the Offset register to make the offset from the start of one
; scan line to the start of the next the desired number of pixels.
; This gives us a virtual screen wider than the actual screen to
; pan across.
; Note that the Offset register is programmed with the logical
; screen width in words, not bytes, hence the final division by 2.
;
	mov	dx,CRTC_INDEX
	mov	ax,(LOGICAL_SCREEN_WIDTH/8/2 shl 8) or HOFFSET
	OUT_WORD
;
; Set the start address to display the memory just past the split
; screen memory.
;
	mov	[StartAddress],SPLIT_SCREEN_HEIGHT*(LOGICAL_SCREEN_WIDTH/8)
	call	SetStartAddress
;
; Set the split screen start scan line.
;
	mov	[SplitScreenLine],SPLIT_SCREEN_START
	call	SetSplitScreenScanLine
;
; Fill the split screen portion of display memory (starting at
; offset 0) with a choppy diagonal pattern sloping left.
;
	mov	ax,VGA_SEGMENT
	mov	es,ax
	sub	di,di
	mov	dx,SPLIT_SCREEN_HEIGHT
				;fill all lines in the split screen
	mov	ax,0FF0h	;starting fill pattern
	cld
RowLoop:
	mov	cx,LOGICAL_SCREEN_WIDTH/8/4
				;fill 1 scan line
ColumnLoop:
	stosw			;draw part of a diagonal line
	mov	word ptr es:[di],0 ;make vertical blank spaces so
				; panning effects can be seen easily
	inc	di
	inc	di
	loop	ColumnLoop
	rol	ax,1		;shift pattern word
	dec	dx
	jnz	RowLoop
;
; Fill the portion of display memory that will be displayed in the
; normal screen (the non-split screen part of the display) with a
; choppy diagonal pattern sloping right.
;
	mov	di,SPLIT_SCREEN_HEIGHT*(LOGICAL_SCREEN_WIDTH/8)
	mov	dx,SCREEN_HEIGHT ;fill all lines
	mov	ax,0c510h	;starting fill pattern
	cld
RowLoop2:
	mov	cx,LOGICAL_SCREEN_WIDTH/8/4
				;fill 1 scan line
ColumnLoop2:
	stosw			;draw part of a diagonal line
	mov	word ptr es:[di],0 ;make vertical blank spaces so
				; panning effects can be seen easily
	inc	di
	inc	di
	loop	ColumnLoop2
	ror	ax,1		;shift pattern word
	dec	dx
	jnz	RowLoop2
;
; Pel pan the non-split screen portion of the display; because
; split screen pel panning suppression is not turned on, the split
; screen jerks back and forth as the pel panning setting cycles.
;
	mov	cx,200	;pan 200 pixels to the left
	call	PanRight
;
; Wait for a key press (don't echo character).
;
	mov	ah,8	;DOS console input without echo function
	int	21h
;
; Return to the original screen location, with pel panning turned off.
;
	mov	[StartAddress],SPLIT_SCREEN_HEIGHT*(LOGICAL_SCREEN_WIDTH/8)
	call	SetStartAddress
	mov	[PelPan],0
	call	SetPelPan
;
; Turn on split screen pel panning suppression, so the split screen
; won't be affected by pel panning. Not done on EGA because both
; readable registers and the split screen pel panning suppression bit
; aren't supported by EGAs.
;
if IS_VGA
	mov	dx,INPUT_STATUS_0
	in	al,dx		;reset the AC Index/Data toggle to
				; Index state
	mov	al,20h+AC_MODE_CONTROL
				;bit 5 set to 1 to keep video on
	mov	dx,AC_INDEX	;point to AC Index/Data register
	out	dx,al
	inc	dx		;point to AC Data reg (for reads only)
	in	al,dx		;get the current AC Mode Control reg
	or	al,20h		;enable split screen pel panning
				; suppression
	dec	dx		;point to AC Index/Data reg (Data for
				; writes only)
	out	dx,al		;write the new AC Mode Control setting
				; with split screen pel panning
				; suppression turned on
endif
;
; Pel pan the non-split screen portion of the display; because
; split screen pel panning suppression is turned on, the split
; screen will not move as the pel panning setting cycles.
;
	mov	cx,200	;pan 200 pixels to the left
	call	PanRight
;
; Wait for a key press (don't echo character).
;
	mov	ah,8	;DOS console input without echo function
	int	21h
;
; Return to text mode and DOS.
;
	mov	ax,0003h	;AH=0 is select mode function
				;AL=3 is mode to select, text mode
	int	10h		;return to text mode
	mov	ah,4ch
	int	21h		;return to DOS
Start	endp
;*********************************************************************
; Waits for the leading edge of the vertical sync pulse.
;
; Input: none
;
; Output: none
;
; Registers altered: AL, DX
;
WaitForVerticalSyncStart	proc	near
	mov	dx,INPUT_STATUS_0
WaitNotVerticalSync:
	in	al,dx
	test	al,08h
	jnz	WaitNotVerticalSync
WaitVerticalSync:
	in	al,dx
	test	al,08h
	jz	WaitVerticalSync
	ret
WaitForVerticalSyncStart	endp
;*********************************************************************
; Waits for the trailing edge of the vertical sync pulse.
;
; Input: none
;
; Output: none
;
; Registers altered: AL, DX
;
WaitForVerticalSyncEnd	proc	near
	mov	dx,INPUT_STATUS_0
WaitVerticalSync2:
	in	al,dx
	test	al,08h
	jz	WaitVerticalSync2
WaitNotVerticalSync2:
	in	al,dx
	test	al,08h
	jnz	WaitNotVerticalSync2
	ret
WaitForVerticalSyncEnd	endp
;*********************************************************************
; Sets the start address to the value specifed by StartAddress.
; Wait for the trailing edge of vertical sync before setting so that
; one half of the address isn't loaded before the start of the frame
; and the other half after, resulting in flicker as one frame is
; displayed with mismatched halves. The new start address won't be
; loaded until the start of the next frame; that is, one full frame
; will be displayed before the new start address takes effect.
;
; Input: none
;
; Output: none
;
; Registers altered: AX, DX
;
SetStartAddress proc	near
	call	WaitForVerticalSyncEnd
	mov	dx,CRTC_INDEX
	mov	al,START_ADDRESS_HIGH
	mov	ah,byte ptr [StartAddress+1]
	cli		;make sure both registers get set at once
	OUT_WORD
	mov	al,START_ADDRESS_LOW
	mov	ah,byte ptr [StartAddress]
	OUT_WORD
	sti
	ret
SetStartAddress endp
;*********************************************************************
; Sets the horizontal pel panning setting to the value specified
; by PelPan. Waits until the start of vertical sync to do so, so
; the new pel pan setting can be loaded during non-display time
; and can be ready by the start of the next frame.
;
; Input: none
;
; Output: none
;
; Registers altered: AL, DX
;
SetPelPan	proc	near
	call	WaitForVerticalSyncStart ;also resets the AC
					; Index/Data toggle
					; to Index state
	mov	dx,AC_INDEX
	mov	al,PEL_PANNING+20h
				;bit 5 set to 1 to keep video on
	out	dx,al		;point the AC Index to Pel Pan reg
	mov	al,[PelPan]
	out	dx,al		;load the new Pel Pan setting
	ret
SetPelPan	endp
;*********************************************************************
; Sets the scan line the split screen starts after to the scan line
; specified by SplitScreenLine.
;
; Input: none
;
; Output: none
;
; All registers preserved
;
SetSplitScreenScanLine	proc	near
	push	ax
	push	cx
	push	dx
;
; Wait for the leading edge of the vertical sync pulse. This ensures
; that we don't get mismatched portions of the split screen setting
; while setting the two or three split screen registers (register 18h
; set but register 7 not yet set when a match occurs, for example),
; which could produce brief flickering.
;
	call	WaitForVerticalSyncStart
;
; Set the split screen scan line.
;
	mov	dx,CRTC_INDEX
	mov	ah,byte ptr [SplitScreenLine]
	mov	al,LINE_COMPARE
	cli		;make sure all the registers get set at once
	OUT_WORD		;set bits 7-0 of the split screen scan line
	mov		ah,byte ptr [SplitScreenLine+1]
	and	ah,1
	mov	cl,4
	shl	ah,cl	;move bit 8 of the split split screen scan
			; line into position for the Overflow reg
	mov	al,OVERFLOW
if IS_VGA
;
; The Split Screen, Overflow, and Line Compare registers all contain
; part of the split screen start scan line on the VGA. We'll take
; advantage of the readable registers of the VGA to leave other bits
; in the registers we access undisturbed.
;
	out	dx,al	;set CRTC Index reg to point to Overflow
	inc	dx	;point to CRTC Data reg
	in	al,dx	;get the current Overflow reg setting
	and	al,not 10h ;turn off split screen bit 8
	or	al,ah	;insert the new split screen bit 8
			; (works in any mode)
	out	dx,al	;set the new split screen bit 8
	dec	dx	;point to CRTC Index reg
	mov	ah,byte ptr [SplitScreenLine+1]
	and	ah,2
	mov	cl,3
	ror	ah,cl	;move bit 9 of the split split screen scan
			; line into position for the Maximum Scan
			; Line register
	mov	al,MAXIMUM_SCAN_LINE
	out	dx,al	;set CRTC Index reg to point to Maximum
			; Scan Line
	inc	dx	;point to CRTC Data reg
	in	al,dx	;get the current Maximum Scan Line setting
	and	al,not 40h ;turn off split screen bit 9
	or	al,ah	;insert the new split screen bit 9
			; (works in any mode)
	out	dx,al	;set the new split screen bit 9
else
;
; Only the Split Screen and Overflow registers contain part of the
; Split Screen start scan line and need to be set on the EGA.
; EGA registers are not readable, so we have to set the non-split
; screen bits of the Overflow register to a preset value, in this
; case the value for 350-scan-line modes.
;
	or	ah,0fh	;insert the new split screen bit 8
			; (only works in 350-scan-line EGA modes)
	OUT_WORD	;set the new split screen bit 8
endif
	sti
	pop	dx
	pop	cx
	pop	ax
	ret
SetSplitScreenScanLine	endp
;*********************************************************************
; Pan horizontally to the right the number of pixels specified by CX.
;
; Input: CX = # of pixels by which to pan horizontally
;
; Output: none
;
; Registers altered: AX, CX, DX
;
PanRight	proc	near
PanLoop:
	inc	[PelPan]
	and	[PelPan],07h
	jnz	DoSetStartAddress
	inc	[StartAddress]
DoSetStartAddress:
	call	SetStartAddress
	call	SetPelPan
	loop	PanLoop
	ret
PanRight	endp
;*********************************************************************
Code	ends
	end	Start
